#!/usr/bin/python3
import json
import os
import requests
import time
import yaml
import sys
from libs.log.logger import log_check


def get_auth_header(project_region):
    """
    Get authorization header
    :param project_region: region of project
    :return: authorized header
    """
    url = os.getenv("AUTH_URL", "https://iam.myhuaweicloud.com/v3/auth/tokens")
    data = {
        "auth": {
            "identity": {
                "methods": [
                    "password"
                ],
                "password": {
                    "user": {
                        "domain": {
                            "name": os.getenv("IAM_DOMAIN", "")
                        },
                        "name": os.getenv("IAM_USER", ""),
                        "password": os.getenv("IAM_PASSWORD", "")
                    }
                }
            },
            "scope": {
                "project": {
                    "name": project_region
                }
            }
        }
    }
    response = requests.post(url, data=json.dumps(data))
    try:
        token = response.headers["X-Subject-Token"]
        header = {'X-Auth-Token': token}
        log_check.info('Get authorized header: Successful')
        return header
    except KeyError:
        log_check.error('Get authorized token: Fail to get auth token.')
        sys.exit(1)


with open('/usr/li-wen/libs/conf/ecs_servers.yaml', 'r', encoding='utf-8') as fp:
    info = yaml.load(fp.read(), Loader=yaml.Loader)
try:
    region = info['region']
    project_id = info['projectId']
except KeyError:
    log_check.error('Get configuration info: Failed to get expected fields from the configure file.')
    sys.exit(1)
headers = get_auth_header(region)


class ECSServers:
    def create(self, arch, flavor_level, admin_pass, count=1):
        """
        Create servers
        :param admin_pass: password for root
        :param arch: architecture of servers
        :param flavor_level: specifies the flavor level of servers
        :param count: numbers of servers
        :return: a list of IP address of the available servers
        """
        if arch not in info['flavorMapping'].keys():
            result = {'code': 400, 'error': 'Unmatched architecture name.'}
            log_check.error('Create servers: {}'.format(result))
            return result
        if flavor_level not in ['l1', 'l2', 'l3']:
            result = {'code': 400, 'error': 'The flavor_level must be one of ["l1", "l2", "l3"].'}
            log_check.error('Create servers: {}'.format(result))
            return result
        if len(admin_pass) < 8 or len(admin_pass) > 26:
            result = {'code': 400, 'error': 'The length of admin_pass must be 8-26.'}
            log_check.error('Create servers: {}'.format(result))
            return result
        if not (type(count) == int and count > 0):
            result = {'code': 400, 'error': 'The count must be a positive integer.'}
            log_check.error('Create servers: {}'.format(result))
            return result
        max_number_can_create = self.get_max_number_can_create()
        if not max_number_can_create:
            result = {'code': 400, 'error': 'Cannot get maximum number of servers that can be created.'}
            log_check.error('Create servers: {}'.format(result))
            return result
        if count > max_number_can_create:
            result = {'code': 400, 'error': 'Exceeds maximum number of servers that can be created.'}
            log_check.error('Create servers: {}'.format(result))
            return result
        url = 'https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers'.format(region, project_id)
        flavorRef = info['flavorMapping'][arch][flavor_level]
        imageRef = info['flavorMapping'][arch]['imageRef']
        name = info['name_prefix'] + str(int(time.time())) + '-' + arch
        vpcid = info['vpcId']
        subnet_id = info['subnetId']
        security_group_id = info['security_group_id']
        volumetype = info['volumetype']
        waiting_time = info['waiting_time']
        query_times = info['query_times']
        server_boot_time = info['server_boot_time']
        data = {
            'server': {
                'adminPass': admin_pass,
                'count': count,
                'flavorRef': flavorRef,
                'imageRef': imageRef,
                'name': name,
                'nics': [
                    {
                        'subnet_id': subnet_id
                    }
                ],
                'root_volume': {
                    'volumetype': volumetype
                },
                'security_groups': [
                    {
                        'id': security_group_id
                    }
                ],
                'vpcid': vpcid
            }
        }
        response = requests.post(url, headers=headers, data=json.dumps(data))
        if response.status_code == 200:
            serverIds = response.json()['serverIds']
            while query_times > 0:
                server_ips = self.get_server_ips(serverIds)
                if len(server_ips) == len(serverIds):
                    result = {'code': 200, 'server_ips': server_ips}
                    log_check.info('Create servers: {}'.format(result))
                    # return after servers startup
                    time.sleep(server_boot_time)
                    return result
                else:
                    query_times -= 1
                    time.sleep(waiting_time)
        else:
            result = {'code': 400,  'error': response.json()}
            log_check.error('Create servers: {}'.format(result))
            return result

    def list(self):
        """
        List all active servers about obs
        :return: a list of all server details
        """
        url = 'https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers/detail?limit=1000&status=ACTIVE&name=obs' \
            .format(region, project_id)
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            servers = [
                {
                    'id': server['id'],
                    'name': server['name'],
                    'ip': server['addresses'].get(list(server['addresses'].keys())[0])[0]['addr'],
                    'status': server['status'],
                    'flavor': {
                        'vcpus': server['flavor']['vcpus'],
                        'ram': server['flavor']['ram']
                    },
                    'arch': self.get_arch(server['flavor']['name'])
                } if server['addresses'] else
                {
                    'id': server['id'],
                    'name': server['name'],
                    'ip': '',
                    'status': server['status'],
                    'flavor': {
                        'vcpus': server['flavor']['vcpus'],
                        'ram': server['flavor']['ram']
                    },
                    'arch': self.get_arch(server['flavor']['name'])
                }
                for server in response.json()['servers']
            ]
            for server in servers:
                try:
                    if not server['ip']:
                        servers.remove(server)
                except KeyError as e:
                    log_check.error('List servers: {}'.format(e))
                    sys.exit(1)
            result = {'code': 200, 'servers': servers}
        else:
            result = {'code': 400, 'error': response.json()}
        return result

    def get(self, server_ip):
        """
        Get a server detail by server_ip
        :param server_ip: IP address
        :return: server detail
        """
        server_id = self.get_server_id(server_ip)
        if not server_id:
            return {'code': 400, 'error': 'Not exist'}
        url = ' https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers/{}'.format(region, project_id, server_id)
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            server = response.json()['server']
            status = server['status']
            vcpus = server['flavor']['vcpus']
            flavor_name = server['flavor']['name']
            ram = server['flavor']['ram']
            if status == 'BUILD':
                status = 'BUILDING'
            elif status not in ['BUILDING', 'ACTIVE', 'DELETED']:
                status = 'OTHER'
            if server['addresses']:
                result = {
                    'id': server['id'],
                    'name': server['name'],
                    'ip': server['addresses'].get(list(server['addresses'])[0])[0]['addr'],
                    'status': status,
                    'flavor': {
                        'vcpus': vcpus,
                        'ram': ram
                    },
                    'arch': self.get_arch(flavor_name)
                }
            else:
                result = {
                    'id': server['id'],
                    'name': server['name'],
                    'ip': '',
                    'status': status,
                    'flavor': {
                        'vcpus': vcpus,
                        'ram': ram
                    },
                    'arch': self.get_arch(flavor_name)
                }
            result = {'code': 200, 'server': result}
            log_check.info('Get server: {}'.format(result))
        else:
            result = {'code': 400, 'error': response.json()}
            log_check.error('Get server: {}'.format(result))
        return result

    def delete(self, serverIps):
        """
        Delete servers
        :param serverIps: a list of IP address
        :return:
        """
        serverIds = self.get_server_ids(serverIps)
        url = 'https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers/delete'.format(region, project_id)
        servers = [{'id': server_id} for server_id in serverIds]
        data = {
            'delete_publicip': True,
            'delete_volume': True,
            'servers': servers
        }
        response = requests.post(url, headers=headers, data=json.dumps(data))
        if response.status_code == 200:
            result = {'code': 200, 'message': 'Delete successfully'}
            log_check.info('Delete servers: {}'.format(serverIps))
        else:
            result = {'code': 400, 'error': response.json()}
            log_check.error('Delete servers: {}'.format(result))
        return result

    @staticmethod
    def get_arch(flavor_name):
        """
        Get architecture by flavor_name
        :param flavor_name: name of flavor
        :return: architecture of the server
        """
        archMapping = info['archMapping']
        for arch in archMapping:
            if flavor_name in archMapping[arch]:
                return arch

    @staticmethod
    def get_server_maps():
        """
        Get mapping between IP addresses and server_ids
        :return: A list of nested dictionaries {ip: server_id}
        """
        url = 'https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers/detail?limit=1000&status=ACTIVE&name=obs' \
            .format(region, project_id)
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            server_maps = [{server['addresses'].get(list(server['addresses'].keys())[0])[0]['addr']: server['id']} if
                           server['addresses'] else {None: server['id']}
                           for server in response.json()['servers']]
            return server_maps

    @staticmethod
    def get_hostname_maps():
        """
        Get mapping between IP addresses and hostnames
        :return: A list of nested dictionaries {IP: hostname}
        """
        url = 'https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers/detail?limit=1000&status=ACTIVE&name=obs' \
            .format(region, project_id)
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            hostname_maps = [{server['addresses'].get(list(server['addresses'].keys())[0])[0]['addr']: server['name']}
                             if server['addresses'] else {None: server['id']}
                             for server in response.json()['servers']]
            return hostname_maps

    def get_hostname(self, server_ip):
        """
        Get hostname of the server through IP address
        :param server_ip: IP address
        :return: hostname of the server
        """
        servers = self.get_hostname_maps()
        for server in servers:
            if server.get(server_ip):
                hostname = server.get(server_ip)
                return hostname

    def get_server_id(self, server_ip):
        """
        Get the server_id through IP address
        :param server_ip: IP address
        :return: server_id
        """
        servers = self.get_server_maps()
        for server in servers:
            if server.get(server_ip):
                server_id = server.get(server_ip)
                return server_id

    def get_server_ids(self, server_ips: list):
        """
        Get a list of server_id through IP addresses
        :param server_ips: list of IP address
        :return: list of server_id
        """
        servers = self.get_server_maps()
        server_ids = []
        for ip in server_ips:
            for server in servers:
                if server.get(ip):
                    server_id = server.get(ip)
                    server_ids.append(server_id)
                    break
        return server_ids

    def get_server_ip(self, server_id: str):
        """
        Get IP address through server_id
        :param server_id: server_id
        :return: IP address
        """
        servers = self.get_server_maps()
        for server in servers:
            if server.get(list(server.keys())[0]) == server_id:
                return list(server.keys())[0]

    def get_server_ips(self, server_ids: list):
        """
        Get a list of IP addresses through server_ids
        :param server_ids: list of server_id
        :return: list of IP address
        """
        servers = self.get_server_maps()
        server_ips = []
        for server_id in server_ids:
            for server in servers:
                server_ip = list(server.keys())[0]
                if server.get(server_ip) == server_id:
                    server_ips.append(server_ip)
                    break
        return server_ips

    @staticmethod
    def get_max_number_can_create():
        """
        Get maximum number of servers can be created
        :return: count of remaining servers can be created or None when error occurs
        """
        url = 'https://ecs.{}.myhuaweicloud.com/v1/{}/cloudservers/detail'.format(region, project_id)
        response = requests.get(url, headers=headers)
        if response.status_code != 200:
            result = {'code': 400, 'error': response.json()}
            log_check.error('Get max number can create: {}'.format(result))
            return
        count = response.json()['count']
        max_servers_number = info['max_servers_number']
        return max_servers_number - count
